#include "src/pbc/portable-format-helper.h"

namespace portable {

PbcScope PbcScope::InvalidScope() { return PbcScope(0); }

PbcFunction PbcScope::GetFunction(const PbcScript& module) const {
  return module.GetFunction(Data()->function_id_);
}

PbcLocalVar PbcScope::GetLocalVar(uint32_t index,
                                  const PbcScript& module) const {
  P_CHECK(index < NumberOfLocalVars());
  return module.GetLocalVar(Data()->first_local_index_ + index);
}

uint32_t PbcScope::GetOuterScopeId() const { return Data()->outer_scope_id_; }

StringRef PbcLocalVar::GetName() const {
  return StringRef(
      Impl()->is_u8stirng ? PbcValueKind::kU8String : PbcValueKind::kU16String,
      Impl()->offset_, Impl()->length_);
}

bool PbcFunction::IsTopLevelFunction(const PbcScript& module) const {
  return address_ == module.GetTopLevelFunction().address_;
}

uint32_t PbcFunction::GetFunctionId(const PbcScript& module) const {
  return static_cast<uint32_t>((address_ - module.GetFunction(0).address_) /
                               sizeof(PbcFunction));
}

PbcBytecode PbcFunction::GetBytecode(const PbcScript& module) const {
  auto* funcHeader = GetFunctionHeader();
  return PbcBytecode(module.BytecodeStorageAddress() +
                     funcHeader->bytecode_offset_);
}

PbcConstantTable PbcFunction::GetConstantTable(const PbcScript& module) const {
  auto* funcHeader = GetFunctionHeader();
  return PbcConstantTable(module.BytecodeStorageAddress() +
                          funcHeader->constant_pool_offset_);
}

StringRef PbcFunction::GetName() const {
  return StringRef(GetFunctionHeader()->is_u8string_ ? PbcValueKind::kU8String
                                                     : PbcValueKind::kU16String,
                   GetFunctionHeader()->name_offset_,
                   GetFunctionHeader()->name_length_);
}

bool PbcFunction::IsNullName() const {
  return GetFunctionHeader()->flags_.is_null_name_;
}

void PbcBytecode::Dump(const PbcScript& module) const {
  std::ostream& os = std::cout;
  const uint8_t* start = FirstBytecode();
  const uint8_t* end = start + BytecodeSizeInBytes();
  os << "Bytecode length: " << BytecodeSizeInBytes() << std::endl;
  const uint8_t* pos = start;
  uint32_t i = 0;
  while (pos < end) {
    // TODO
    if (i > 0 && i % 8 == 0) {
      os << std::endl;
    }
    printf("%02x ", *pos);
    ++pos;
    ++i;
  }
  os << std::endl;
}

void PbcConstantTable::DumpShortValue(const PbcScript& module) const {
  std::ostream& os = std::cout;
  uint32_t numEntries = NumberOfEntries();
  os << "Constant pool (size = " << numEntries << ")" << std::endl;
  const PbcShortValue* entries = ShortEntries();
  for (uint32_t i = 0; i < numEntries; ++i) {
    const PbcShortValue& entry = entries[i];
    os << "  " << i << ": <";
    const char* kind_name = GetPbcValueKindName(ToNormalKind(entry.Kind()));
    os << kind_name;
    switch (entry.Kind()) {
      case PbcShortValueKind::kUniqueValue: {
        const char* tag_name =
            GetUniqueTagName(static_cast<const PbcShortUnique&>(entry).Tag());
        os << "[" << tag_name << "]> ";
        break;
      }
      case PbcShortValueKind::kShortSmi: {
        uint32_t smi = static_cast<const PbcShortSmi&>(entry).GetValue();
        os << "[" << smi << "]> ";
        break;
      }
      case PbcShortValueKind::kShortU8String: {
        std::string_view str_view =
            module.GetString(static_cast<const PbcShortStringRef&>(entry));
        os << "[" << str_view.size() << "]> ";
        os << str_view;
        break;
      }
      case PbcShortValueKind::kShortU16String: {
        std::u16string_view str_view =
            module.GetU16String(static_cast<const PbcShortStringRef&>(entry));
        os << "[" << str_view.size() << "]> ";
        for (auto c : str_view) {
          std::wcout << static_cast<wchar_t>(c);
        }
        break;
      }
      case PbcShortValueKind::kShortFunction: {
        StringRef name =
            module
                .GetFunction(
                    static_cast<const PbcShortFunction&>(entry).GetFuncId())
                .GetName();
        os << "> ";
        if (name.Kind() == PbcValueKind::kU8String) {
          os << module.GetString(name);
        } else {
          std::u16string_view str_view = module.GetU16String(name);
          for (auto c : str_view) {
            std::wcout << static_cast<wchar_t>(c);
          }
        }
        break;
      }
      case PbcShortValueKind::kShortScope: {
        uint32_t scope_id =
            static_cast<const PbcShortScope&>(entry).GetScopeId();
        os << "[scope_" << scope_id << "]> ";
        break;
      }
      case PbcShortValueKind::kShortArrayLiteral: {
        PbcArrayLiteral array_literal = module.GetArrayLiteral(
            static_cast<const PbcShortLiteralRef&>(entry));
        os << "[" << array_literal.NumberOfElements() << "]> ";
        break;
      }
      case PbcShortValueKind::kShortObjectLiteral: {
        PbcDefaultObjectLiteral object_literal = module.GetDefaultObjectLiteral(
            static_cast<const PbcShortLiteralRef&>(entry));
        os << "[" << object_literal.NumberOfProperties() << "]> ";
        break;
      }
      default: {
        break;
      }
    }
    os << std::endl;
  }
}

void PbcConstantTable::Dump(const PbcScript& module) const {
  if (IsShortValue()) {
    return DumpShortValue(module);
  }

  std::ostream& os = std::cout;
  uint32_t numEntries = NumberOfEntries();
  os << "Constant pool (size = " << numEntries << ")" << std::endl;
  const PbcValue* entries = Entries();
  for (uint32_t i = 0; i < numEntries; ++i) {
    const PbcValue& entry = entries[i];
    os << "  " << i << ": <";
    const char* kind_name = GetPbcValueKindName(entry.Kind());
    os << kind_name;
    switch (entry.Kind()) {
      case PbcValueKind::kUniqueValue: {
        const char* tag_name =
            GetUniqueTagName(static_cast<const UniqueScalar&>(entry).Tag());
        os << "[" << tag_name << "]> ";
        break;
      }
      case PbcValueKind::kSmi: {
        int smi = static_cast<const SmiScalar&>(entry).GetValue();
        os << "[" << smi << "]> ";
        break;
      }
      case PbcValueKind::kU8String: {
        std::string_view str_view =
            module.GetString(static_cast<const StringRef&>(entry));
        os << "[" << str_view.size() << "]> ";
        os << str_view;
        break;
      }
      case PbcValueKind::kU16String: {
        std::u16string_view str_view =
            module.GetU16String(static_cast<const StringRef&>(entry));
        os << "[" << str_view.size() << "]> ";
        for (auto c : str_view) {
          std::wcout << static_cast<wchar_t>(c);
        }
        break;
      }
      case PbcValueKind::kFunction: {
        StringRef name =
            module
                .GetFunction(static_cast<const FunctionRef&>(entry).GetFuncId())
                .GetName();
        os << "> ";
        if (name.Kind() == PbcValueKind::kU8String) {
          os << module.GetString(name);
        } else {
          std::u16string_view str_view = module.GetU16String(name);
          for (auto c : str_view) {
            std::wcout << static_cast<wchar_t>(c);
          }
        }
        break;
      }
      case PbcValueKind::kDouble: {
        os << "> ";
        os << module.GetDouble(static_cast<const DoubleRef&>(entry));
        break;
      }
      case PbcValueKind::kBigInt: {
        os << "> ";
        os << module.GetBigInt(static_cast<const BigIntRef&>(entry));
        break;
      }
      case PbcValueKind::kArrayLiteral: {
        PbcArrayLiteral array_literal =
            module.GetArrayLiteral(static_cast<const LiteralRef&>(entry));
        os << "[" << array_literal.NumberOfElements() << "]> ";
        break;
      }
      case PbcValueKind::kDefaultObjectLiteral: {
        PbcDefaultObjectLiteral object_literal = module.GetDefaultObjectLiteral(
            static_cast<const LiteralRef&>(entry));
        os << "[" << object_literal.NumberOfProperties() << "]> ";
        break;
      }
      case PbcValueKind::kObjectLiteral: {
        PbcObjectLiteral object_literal =
            module.GetObjectLiteral(static_cast<const LiteralRef&>(entry));
        os << "[" << object_literal.NumberOfProperties() << "]> ";
        break;
      }
      case PbcValueKind::kTemplateLiteral: {
        PbcTemplateLiteral template_literal =
            module.GetTemplateLiteral(static_cast<const LiteralRef&>(entry));
        os << "[" << template_literal.Length() << " "
           << template_literal.IsDuplicated();
        os << "]> ";
        break;
      }
      case PbcValueKind::kScope: {
        uint32_t scope_id = static_cast<const ScopeRef&>(entry).GetScopeId();
        os << "[scope_" << scope_id << "]> ";
        break;
      }
      default: {
        break;
      }
    }
    os << std::endl;
  }
}

void PbcFeedbackMeta::Dump(const PbcScript& module) const {
  std::ostream& os = std::cout;
  os << "Feedback Metadata (num_kinds = " << NumberOfKinds()
     << ", num_closure_parameter_counts = " << NumberOfClosureParameterCounts()
     << ")" << std::endl;
}

void PbcFunction::DumpHeader(const PbcScript& module) const {
  std::ostream& os = std::cout;
  const FunctionHeader* header = GetFunctionHeader();
  os << "func name: ";
  StringRef name = GetName();
  if (name.Kind() == PbcValueKind::kU8String) {
    os << module.GetString(name) << std::endl;
  } else {
    std::u16string_view str_view = module.GetU16String(name);
    for (auto c : str_view) {
      std::wcout << static_cast<wchar_t>(c);
    }
  }
  os << "flags: " << std::endl;
  os << "  is_copyable_: " << header->flags_.is_copyable_ << std::endl;
  os << "  is_strict_mode_: " << header->flags_.is_strict_mode_ << std::endl;
  os << "  is_async_: " << header->flags_.is_async_ << std::endl;
  os << "  is_generator_: " << header->flags_.is_generator_ << std::endl;
  os << "  is_null_name_: " << header->flags_.is_null_name_ << std::endl;
  os << "register_count_: " << header->register_count_ << std::endl;
  os << "parameter_count_: " << header->parameter_count_ << std::endl;
  os << "bytecode_offset_: " << header->bytecode_offset_ << std::endl;
  os << "bytecode_size_: " << GetBytecode(module).BytecodeSizeInBytes()
     << std::endl;
  os << "constant_pool_offset_: " << header->constant_pool_offset_ << std::endl;
  os << "constant_pool_size_: "
     << GetConstantTable(module).NumberOfEntries() * sizeof(PbcValue)
     << std::endl;
  os << "scope id: " << GetScopeId(module) << std::endl;
}

void PbcFunction::Dump(const PbcScript& module) const {
  std::ostream& os = std::cout;
  DumpHeader(module);
  GetBytecode(module).Dump(module);
  GetConstantTable(module).Dump(module);
  os << std::endl;
}

void PbcScope::Dump(const PbcScript& module) const {
  std::ostream& os = std::cout;
  if (!IsValid()) {
    os << "<invalid scope>" << std::endl;
    return;
  }
  PbcFunction func = GetFunction(module);
  os << "<scope" << "> [locals: " << NumberOfLocalVars()
     << ", type: " << static_cast<uint32_t>(GetScopeType())
     << ", func_id: " << Data()->function_id_ << ", func: ";

  StringRef func_name = func.GetName();
  if (func_name.Kind() == PbcValueKind::kU8String) {
    os << module.GetString(func_name);
  } else {
    std::u16string_view str_view = module.GetU16String(func_name);
    for (auto c : str_view) {
      std::wcout << static_cast<wchar_t>(c);
    }
  }
  os << "]" << std::endl;
  for (uint32_t i = 0; i < NumberOfLocalVars(); ++i) {
    os << "  " << i << ": ";
    StringRef name = GetLocalVar(i, module).GetName();
    if (name.Kind() == PbcValueKind::kU8String) {
      os << module.GetString(name);
    } else {
      std::u16string_view str_view = module.GetU16String(name);
      for (auto c : str_view) {
        std::wcout << static_cast<wchar_t>(c);
      }
    }
    os << std::endl;
  }
}

void PbcScript::DumpHeader() const {
  std::ostream& os = std::cout;
  os << "magic_: " << header.magic_ << std::endl;
  os << "version_: " << header.version_ << std::endl;
  os << "top_level_func_id_: " << header.top_level_func_id_ << std::endl;

  os << "top_declarations_offset_: " << header.top_declarations_offset_
     << std::endl;
  os << "top_declarations_size_: " << header.top_declarations_size_
     << std::endl;
  os << "top_declarations_count_: " << header.top_declarations_count_
     << std::endl;

  os << "double_table_offset_: " << header.double_table_offset_ << std::endl;
  os << "double_table_size_: " << header.double_table_size_ << std::endl;
  os << "double_count_: " << header.double_count_ << std::endl;

  os << "string_storage_offset_: " << header.string_storage_offset_
     << std::endl;
  os << "string_storage_size_: " << header.string_storage_size_ << std::endl;
  os << "string_count_: " << header.string_count_ << std::endl;

  os << "u16string_storage_offset_: " << header.u16string_storage_offset_
     << std::endl;
  os << "u16string_storage_size_: " << header.u16string_storage_size_
     << std::endl;
  os << "u16string_count_: " << header.u16string_count_ << std::endl;

  os << "bigint_storage_offset_: " << header.bigint_storage_offset_
     << std::endl;
  os << "bigint_storage_size_: " << header.bigint_storage_size_ << std::endl;
  os << "bigint_count_: " << header.bigint_count_ << std::endl;

  os << "literal_storage_offset_: " << header.literal_storage_offset_
     << std::endl;
  os << "literal_storage_size_: " << header.literal_storage_size_ << std::endl;
  os << "literal_count_: " << header.literal_count_ << std::endl;

  os << "scope_storage_offset_: " << header.scope_storage_offset_ << std::endl;
  os << "scope_storage_size_: " << header.scope_storage_size_ << std::endl;
  os << "scope_count_: " << header.scope_count_ << std::endl;

  os << "function_table_offset_: " << header.function_table_offset_
     << std::endl;
  os << "function_table_size_: " << header.function_table_size_ << std::endl;
  os << "function_count_: " << header.function_count_ << std::endl;

  os << "bytecode_storage_offset_: " << header.bytecode_storage_offset_
     << std::endl;
  os << std::endl;
}

void PbcScript::DumpScopes() const {
  std::ostream& os = std::cout;
  uint32_t num_scopes = NumberOfScopes();
  uintptr_t start = ScopeStorageAddress();
  uintptr_t addr = start;
  for (uint32_t i = 0; i < num_scopes; ++i) {
    PbcScope scope(addr);
    scope.Dump(*this);
    addr += scope.SizeInByte();
  }
  os << std::endl;
}

void PbcScript::Dump() const {
  DumpHeader();
  DumpScopes();
  auto n = NumberOfFunctions();
  for (uint32_t i = 0; i < n; ++i) {
    PbcFunction func = GetFunction(i);
    func.Dump(*this);
  }
}

}  // namespace portable